﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Web.Routing;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using ReadyApps.Common;
using ReadyApps.Data;
using ReadyApps.Wiki;

namespace ReadyApps.Web.UI
{
    /// <summary>
    /// Displays ReadyApps wiki information to the user in a single control.
    /// </summary>
    /// <remarks>
    /// This class acts as the interface between the ASP.Net control hierarchy
    /// and the ReadyApps internal objects. This was done to achieve a more testable
    /// component.
    /// </remarks>
    public class WikiDisplay : System.Web.UI.WebControls.CompositeControl, INamingContainer
    {
        #region Variables

        private WikiController wikiController;
        private WikiPageState wikiPageState;
        private WikiControlSettings controlSettings;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets a value indicating the internal wiki site that the <see cref="WikiDisplay"/>
        /// page control references content from.
        /// </summary>
        public string SiteName
        {
            get { return (string)ViewState["SiteName"] ?? string.Empty; }
            set { ViewState["SiteName"] = value; }
        }

        /// <summary>
        /// Gets or sets the full internal folder path that the <see cref="WikiDisplay"/> is based on.
        /// </summary>
        public string BaseFolder
        {
            // TODO: Add validation logic in a common component
            get { return (string)ViewState["BaseFolder"] ?? string.Empty; }
            set { ViewState["BaseFolder"] = value; }
        }

        /// <summary>
        /// Gets or sets the path to the CKEditor script files on the server which can be an
        /// application-based path.
        /// </summary>
        public string CKEditorBasePath
        {
            get { return controlSettings.CKEditorBasePath; }
            set { controlSettings.CKEditorBasePath = value; }
        }

        /// <summary>
        /// Gets the <see cref="HtmlTextWriterTag"/> for the rendered output of the
        /// <see cref="WikiDisplay"/>.
        /// </summary>
        protected override HtmlTextWriterTag TagKey
        {
            get { return HtmlTextWriterTag.Div; }
        }

        #endregion

        #region Templates

        /// <summary>
        /// Gets or sets the layout template for a normal wiki page display.
        /// </summary>
        /// <remarks>
        /// The supplied template must contain a server control with an ID of 'contentPlaceHolder'
        /// to instantiate the wiki page content in and a server control with an ID of
        /// 'controlsPlaceHolder' to instantiate the wiki control buttons in. If either of these
        /// is not found, a runtime exception will be thrown.
        /// </remarks>
        [
        Browsable(false),
        DefaultValue(null),
        Description("The layout template for a normal wiki page display."),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
        public ITemplate LayoutTemplate
        {
            get { return controlSettings.LayoutTemplate; }
            set { controlSettings.LayoutTemplate = value; }
        }

        /// <summary>
        /// Gets or sets the layout template for an editing wiki page display.
        /// </summary>
        /// <remarks>
        /// The supplied template must contain a server control with an ID of 'contentPlaceHolder'
        /// to instantiate the wiki page content editor in and a server control with an ID of
        /// 'controlsPlaceHolder' to instantiate the wiki control buttons in. If either of these
        /// is not found, a runtime exception will be thrown.
        /// </remarks>
        [
        Browsable(false),
        DefaultValue(null),
        Description("The layout template for an editing wiki page display."),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
        public ITemplate EditLayoutTemplate
        {
            get { return controlSettings.EditLayoutTemplate; }
            set { controlSettings.EditLayoutTemplate = value; }
        }

        /// <summary>
        /// Gets or sets the template to display to users if a non-existent page is requested.
        /// </summary>
        [
        Browsable(false),
        DefaultValue(null),
        Description("The view displayed when a non-existent page is requested."),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
        public ITemplate PageNotFoundTemplate
        {
            get { return controlSettings.PageNotFoundTemplate; }
            set { controlSettings.PageNotFoundTemplate = value; }
        }

        /// <summary>
        /// Gets or sets the template to display to users that do not have sufficient privileges
        /// to view a wiki page.
        /// </summary>
        [
        Browsable(false),
        DefaultValue(null),
        Description("The view displayed when the user does not have sufficient privileges to view a wiki page."),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
        public ITemplate PageNotAuthorizedTemplate
        {
            get { return controlSettings.PageNotAuthorizedTemplate; }
            set { controlSettings.PageNotAuthorizedTemplate = value; }
        }

        /// <summary>
        /// Gets or sets a template used to create wiki control buttons.
        /// </summary>
        /// <remarks>
        /// The supplied template must contain a server control with an ID of 'buttonControl'
        /// that implements <see cref="System.Web.UI.WebControls.IButtonControl"/>. If a control
        /// that meets these criteria is not found, a runtime exception will be thrown.
        /// </remarks>
        [
        Browsable(false),
        DefaultValue(null),
        Description("The template used to generate wiki control buttons."),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
        public ITemplate ButtonTemplate
        {
            get { return controlSettings.ButtonTemplate; }
            set { controlSettings.ButtonTemplate = value; }
        }

        #endregion

        #region Events

        private event EventHandler<TitleSetEventArgs> titleSet;

        /// <summary>
        /// The event that is called when the title of the wiki page is set. This gives
        /// developers access to write the wiki page title to any place they would like
        /// to put it.
        /// </summary>
        public event EventHandler<TitleSetEventArgs> TitleSet
        {
            add { titleSet += value; }
            remove { titleSet -= value; }
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Creates a new <see cref="WikiDisplay"/> page control.
        /// </summary>
        public WikiDisplay()
        {
            TraceManager.BeginMethod(TraceComponent.Display, "Ctor");

            controlSettings = new WikiControlSettings();
            controlSettings.StatusChange += new EventHandler<WikiPageStatusEventArgs>(controlSettings_StatusChange);
            controlSettings.IncludeResource += new EventHandler<IncludeResourceEventArgs>(controlSettings_IncludeScriptResource);
            controlSettings.TitleSet += new EventHandler<TitleSetEventArgs>(controlSettings_TitleSet);
            controlSettings.Redirect += new EventHandler<RedirectEventArgs>(controlSettings_Redirect);

            TraceManager.EndMethod(TraceComponent.Display, "Ctor");
        }

        #endregion

        #region Method Overrides

        /// <summary>
        /// Performs normal control initialization tasks as well as registering the
        /// control for control state persistence.
        /// </summary>
        /// <param name="e">The <see cref="EventArgs"/> for the Init event.</param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            // Required to enable control state across postbacks
            Page.RegisterRequiresControlState(this);
        }

        /// <summary>
        /// Returns the control state to be persisted across page postbacks.
        /// </summary>
        /// <returns>The control state object</returns>
        protected override object SaveControlState()
        {
            TraceManager.ExecuteMethod(TraceComponent.Display, "SaveControlState");
            
            return new Pair(base.SaveControlState(), wikiPageState);
        }

        /// <summary>
        /// Parses and loads the persisted control state from the given state object.
        /// </summary>
        /// <param name="savedState">The control state object to parse</param>
        protected override void LoadControlState(object savedState)
        {
            TraceManager.BeginMethod(TraceComponent.Display, "LoadControlState");

            if (savedState != null)
            {
                Pair savedStates = savedState as Pair;
                Debug.Assert(savedStates != null);
                base.LoadControlState(savedStates.First);
                wikiPageState = savedStates.Second as WikiPageState;
            }
            else
            {
                base.LoadControlState(savedState);
            }

            TraceManager.EndMethod(TraceComponent.Display, "LoadControlState");
        }

        /// <summary>
        /// Creates the initial control tree for the composite control.
        /// </summary>
        protected override void CreateChildControls()
        {
            TraceManager.BeginMethod(TraceComponent.Display, "CreateChildControls");

            if (wikiPageState == null)
            {
                wikiPageState = new WikiPageState();
            }

            // To allow users to go directly to the wiki page without routing, we will need to
            // determine the route they came by.
            RouteBase requestRoute = Page.Request.RequestContext.RouteData.Route;
            if (requestRoute == null) // The user got here by going directly to the page
            {
                string routeUrl = ReadyAppsSettings.WikiMappings
                    .First(m => m.MapTo.Equals(Page.Request.AppRelativeCurrentExecutionFilePath, StringComparison.CurrentCultureIgnoreCase))
                    .GetRouteUrl();
                requestRoute = new Route(routeUrl, null);
            }

            wikiController = new WikiController(
                new WikiPageRequest(SiteName, BaseFolder, Page.User.Identity.Name, (string)Page.RouteData.Values[Application.PATH_DATA_KEY],
                    Page.IsPostBack),
                wikiPageState,
                controlSettings,
                WikiDataProviderFactory.GetInstance(),
                new WikiUrlManager(BaseFolder, requestRoute),
                new WikiRoleManager(),
                Controls);
            wikiController.CreateControls();

            TraceManager.EndMethod(TraceComponent.Display, "CreateChildControls");
        }

        /// <summary>
        /// This method is called before the Render phase to complete any data binding
        /// and control generation after a post back.
        /// </summary>
        /// <param name="e">The <see cref="EventArgs"/> for the PreRender event</param>
        protected override void OnPreRender(EventArgs e)
        {
            TraceManager.BeginMethod(TraceComponent.Display, "OnPreRender");
            
            base.OnPreRender(e);
            wikiController.OnPreRender();

            TraceManager.EndMethod(TraceComponent.Display, "OnPreRender");
        }

        #endregion

        #region Private Methods

        private void controlSettings_StatusChange(object sender, WikiPageStatusEventArgs e)
        {
            TraceManager.BeginMethod(TraceComponent.Display, "controlSettings_StatusChange");

            TraceManager.VariableValue(TraceComponent.Display, "StatusCode", e.StatusCode);
            Page.Response.StatusCode = (int)e.StatusCode;

            TraceManager.EndMethod(TraceComponent.Display, "controlSettings_StatusChange");
        }

        private int includeNumber = 1;
        private void controlSettings_IncludeScriptResource(object sender, IncludeResourceEventArgs e)
        {
            TraceManager.BeginMethod(TraceComponent.Display, "controlSettings_IncludeScriptResource");

            TraceManager.VariableValue(TraceComponent.Display, "IncludeType", e.IncludeType);
            switch (e.IncludeType)
            {
                case IncludeType.ScriptFile:
                    // fix for bug 605 - http://readyapps.codeplex.com/workitem/605
                    //Page.ClientScript.RegisterClientScriptResource(typeof(WikiDisplay), e.Resource);
                    Literal script = new Literal();
                    script.Text = "<script type=\"text/javascript\" src=\"" +
                        Page.ClientScript.GetWebResourceUrl(typeof(WikiDisplay), e.Resource) + "\"></script>";
                    Page.Header.Controls.Add(script);
                    break;
                case IncludeType.ScriptBlock:
                    Page.ClientScript.RegisterClientScriptBlock(typeof(WikiDisplay), includeNumber++.ToString(), e.Resource, true);
                    break;
                case IncludeType.Stylesheet:
                    HtmlLink link = new HtmlLink();
                    link.Href = Page.ClientScript.GetWebResourceUrl(typeof(WikiDisplay), e.Resource);
                    link.Attributes.Add("rel", "stylesheet");
                    link.Attributes.Add("type", "text/css");
                    Page.Header.Controls.Add(link);
                    break;
                default:
                    break;
            }

            TraceManager.EndMethod(TraceComponent.Display, "controlSettings_IncludeScriptResource");
        }

        private void controlSettings_TitleSet(object sender, TitleSetEventArgs e)
        {
            TraceManager.BeginMethod(TraceComponent.Display, "controlSettings_TitleSet");

            if (titleSet != null)
            {
                titleSet(sender, e);
            }

            TraceManager.EndMethod(TraceComponent.Display, "controlSettings_TitleSet");
        }

        private void controlSettings_Redirect(object sender, RedirectEventArgs e)
        {
            TraceManager.BeginMethod(TraceComponent.Display, "controlSettings_Redirect");

            Page.Response.Redirect(e.RedirectUrl);

            TraceManager.EndMethod(TraceComponent.Display, "controlSettings_Redirect");
        }

        #endregion
    }
}
